// Author: dnorlov@google.com (Dmitry Orlov)

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "assembler_parser.h"

namespace assembler {

const char *Parser::CommandName[NUM_COMMAND_TYPE] = {
  "",  // Invalid command
  "MOV",
  "ADD",
  "SUB",
  "MULT",
  "DIV",
  "CALL",
  "RET",
  "PUSH",
  "POP" };

Lexer::Lexer(const char *stream, int length)
: str_(stream), end_(str_ + length) {
}

bool Lexer::done() {
  SkipSpaces();
  return (str_ == end_);
}

bool Lexer::LooksLikeFunctionName(const char *str, int len) {
  if (len < 1)
    return false;
  if (*str != '@')
    return false;
  const char *end = str + len;
  ++str;
  while (str < end) {
    if (!isalpha(*str) && !isdigit(*str))
      return false;
    ++str;
  }
  return true;
}

bool Lexer::LooksLikeRegisterName(const char *str, int len) {
  return (len == 2 && str[0] == 'F' && isdigit(str[1]));
}

bool Lexer::LooksLikeCommandName(const char *str, int len) {
  if (len == 0) return false;
  const char *end = str + len;
  while (str < end) {
    if (*str < 'A' || *str > 'Z')
      return false;
    ++str;
  }
  return true;
}

bool Lexer::LooksLikeConstant(const char *str, int len) {
  if (len == 0)
    return false;
  const char *end = str + len;
  if (*str == '-') {
    ++str;
  }
  int before_period = 0;
  while (str < end && isdigit(*str)) {
    ++str;
    ++before_period;
  }
  if (str < end && *str == '.') {
    ++str;
    int after_period = 0;
    while (str < end && isdigit(*str)) {
      ++after_period;
      ++str;
    }
    return (str == end && after_period > 0);
  } else {
    return (str == end && before_period > 0);
  }
}

void Lexer::SkipSpaces() {
  while (str_ < end_ && isspace(*str_))
    ++str_;
}

bool Lexer::ConsumeLexem(Lexem *result) {
  SkipSpaces();
  if (str_ == end_)
    return false;
  result->lexem_str = str_;
  result->length = 0;
  if (IsPunctuation(*str_)) {
    // Comma, single-character lexem
    result->lexem_str = str_;
    result->length = 1;
    result->type = Lexem::COMMA;
    ++str_;
    return true;
  }
  if (IsIncorrectSymbol(*str_)) {
    result->type = Lexem::NO_TYPE;
    while (str_ < end_ && IsIncorrectSymbol(*str_))
      ++str_;
    return true;
  }
  while (str_ < end_ && IsCorrectNonPunctuationSymbol(*str_) &&
    !isspace(*str_)) {
    ++str_;
  }
  result->length = str_ - result->lexem_str;
  if (LooksLikeCommandName(result->lexem_str, result->length)) {
    result->type = Lexem::COMMAND_NAME;
  } else if (LooksLikeRegisterName(result->lexem_str, result->length)) {
    result->type = Lexem::REGISTER_NAME;
  } else if (LooksLikeFunctionName(result->lexem_str, result->length)) {
    result->type = Lexem::FUNCTION_NAME;
  } else if (LooksLikeConstant(result->lexem_str, result->length)) {
    result->type = Lexem::CONSTANT;
  } else {
    result->type = Lexem::NO_TYPE;
  }
  return true;
}

bool Lexer::IsCorrectNonPunctuationSymbol(char c) {
  return (isalpha(c) || isdigit(c) || c == '@' ||
    c == '.' || c == '-');
}

bool Lexer::IsPunctuation(char c) {
  return c == ',';
}

bool Lexer::IsIncorrectSymbol(char c) {
  return !IsCorrectNonPunctuationSymbol(c) && !IsPunctuation(c) && !isspace(c);
}

Parser::Parser(Lexer *lexer)
: lexer_(lexer), invalid_state_(false), last_command_(INVALID_COMMAND) {
}

bool Parser::ConsumeCommand() {
  if (invalid_state_)
    return false;
  if (lexer_->done())
    return false;
  CommandType command_type = ExpectCommand();
  if (invalid_state_)
    return false;
  int arg_count = CommandArgumentInfo::ArgumentCount[command_type];
  if (arg_count == 0) {
    last_command_ = Command(command_type);
  } else if (arg_count == 1) {
    CommandArgument argument = ExpectArgument();
    if (invalid_state_)
      return false;
    last_command_ = Command(command_type, argument);
  } else {
    // arg_count = 2
    CommandArgument arg1 = ExpectArgument();
    ExpectComma();
    CommandArgument arg2 = ExpectArgument();
    if (invalid_state_)
      return false;
    last_command_ = Command(command_type, arg1, arg2);
  }
  return true;
}

CommandArgument Parser::ExpectArgument() {
  Lexem lexem;
  if (lexer_->ConsumeLexem(&lexem)) {
    CommandArgument result;
    if (lexem.type == Lexem::REGISTER_NAME) {
      result.type = ARGUMENT_REGISTER;
      result.arg.register_id = lexem.lexem_str[1] - '0';
      return result;
    } else if (lexem.type == Lexem::FUNCTION_NAME) {
      result.type = ARGUMENT_FUNCTION_NAME;
      result.arg.function.name = lexem.lexem_str + 1;
      result.arg.function.length = lexem.length - 1;
      return result;
    } else if (lexem.type == Lexem::CONSTANT) {
      result.type = ARGUMENT_CONSTANT;
      const char *endptr = lexem.lexem_str + lexem.length;
      result.arg.constant_value = strtod(lexem.lexem_str,
        const_cast<char**>(&endptr));
      return result;
    }
  }
  return CommandArgument();
  invalid_state_ = true;  
}

void Parser::ExpectComma() {
  Lexem lexem;
  if (lexer_->ConsumeLexem(&lexem)) {
    if (lexem.type == Lexem::COMMA)
      return;
  }
  invalid_state_ = true;
}

CommandType Parser::ExpectCommand() {
  Lexem lexem;
  if (lexer_->ConsumeLexem(&lexem)) {
    if (lexem.type == Lexem::COMMAND_NAME) {
      CommandType type = CommandLexemToType(lexem);
      if (type != INVALID_COMMAND)
        return type;
    }
  }
  invalid_state_ = true;
  return INVALID_COMMAND;
}

CommandType Parser::CommandLexemToType(const Lexem &lexem) {
  for (int type = INVALID_COMMAND; type < NUM_COMMAND_TYPE; ++type)
    if (!strncmp(lexem.lexem_str, CommandName[type], lexem.length)) {
      return static_cast<CommandType>(type);
    }
  return INVALID_COMMAND;
}

}  // namespace assembler